package compile

import (
	"fmt"
	"io"
	"io/ioutil"
	"strconv"
	"unicode"
	"unicode/utf8"
)

const (
	BASE_LINE = 1
	BASE_COL  = 1
)

type scanState struct {
	ln, col int

	cp    int
	ch    rune
	isEOF bool

	pos Pos

	tok         token
	tokOp       operator
	tokValue    string
	tokNumType  numType
	tokNumBase  numBase
	tokAssignOp operator
}

type scanner struct {
	source     []byte
	sourceName string
	sourceSize int
	line, col  int

	cp int

	ch rune

	isEOF bool

	pos *Pos

	tok         token
	tokOp       operator // valid if tok == _OPERATOR
	tokValue    string   // valid if tok == _IDENTIFIER or _NUMBER
	tokNumType  numType
	tokNumBase  numBase
	tokAssignOp operator // valid if tok == _ASSIGN_OP

	errHandler SyntaxErrorHandler
}

func (self *scanner) init(source io.Reader, name string) {
	sour, err := ioutil.ReadAll(source)
	if err != nil {
		plainError(err.Error(), true)
	}

	if len(sour) == 0 {
		self.ch = ' '
	} else {
		self.ch = rune(sour[0])
	}

	self.source = sour
	self.sourceSize = len(sour)
	self.sourceName = name
	self.line = BASE_LINE
	self.col = BASE_COL
	self.cp = 0
	self.isEOF = false
	self.pos = &Pos{self.line, self.col}
	self.errHandler = defaultSyntaxErrorHandler
}

func (self *scanner) setErrHandler(errh SyntaxErrorHandler) {
	self.errHandler = errh
}

func (self *scanner) makeState() *scanState {
	return &scanState{
		ch:          self.ch,
		cp:          self.cp,
		isEOF:       self.isEOF,
		pos:         *self.pos,
		tok:         self.tok,
		tokAssignOp: self.tokAssignOp,
		tokNumBase:  self.tokNumBase,
		tokOp:       self.tokOp,
		tokValue:    self.tokValue,
		ln:          self.line,
		col:         self.col,
	}
}

func (self *scanner) setState(state *scanState) {
	self.ch = state.ch
	self.cp = state.cp
	self.isEOF = state.isEOF
	self.pos = &state.pos
	self.tok = state.tok
	self.tokAssignOp = state.tokAssignOp
	self.tokNumBase = state.tokNumBase
	self.tokOp = state.tokOp
	self.tokValue = state.tokValue
	self.line = state.ln
	self.col = state.col
}

func (self *scanner) nextch() (ch rune) {
	if self.eof() {
		self.isEOF = true
		return
	}

	ch, w := utf8.DecodeRune(self.source[self.cp:])

	self.setp(w)
	self.ch, _ = utf8.DecodeRune(self.source[self.cp:])

	return
}

func (self *scanner) peekString(size int) string {
	tcp := self.cp + 1
	buf := make([]rune, 0)

	count := 0

	for ; tcp < self.sourceSize && count < size; count++ {
		r, w := utf8.DecodeRune(self.source[tcp:])
		buf = append(buf, r)
		tcp += w
	}

	return string(buf)
}

func (self *scanner) nowch() (ch rune, w int) {
	if self.eof() {
		return
	}
	return utf8.DecodeRune(self.source[self.cp:])
}

func (self *scanner) movcp(step int) {
	self.setp(step - 1)
	self.nextch()
}

func (self *scanner) syntaxError(msg string, exit bool) {
	self._syntaxError(msg, exit, 0)
}

func (self *scanner) _syntaxError(msg string, exit bool, goBack int) {
	ofs := 0
	if self.col-goBack >= 0 {
		ofs = self.col - goBack
	}

	self.errHandler(
		self.line, self.col, ofs,
		self.source[self.cp-(self.col-1):], msg, self.sourceName, exit)
}

func (self *scanner) eof() bool {
	return (self.cp >= self.sourceSize && self.cp >= 0) || self.isEOF
}

func (self *scanner) setp(v int) {
	self.cp += v
	self.col += v
}

func (self *scanner) rewind() (ch rune, w int) {
	_, w = self.nowch()
	self.setp(-w)

	if self.eof() {
		return
	}

	self.ch, _ = self.nowch()

	return self.nowch()
}

func (self *scanner) nextToken() *Token {
	self.next()
	return &Token{
		value:   self.tokValue,
		op:      self.tokOp,
		kind:    self.tok,
		numType: self.tokNumType,
		numBase: self.tokNumBase,
		pos:     *self.pos, /* copy */
	}
}

func (self *scanner) newLine() {
	self.col = BASE_COL - 1
	self.line++
}

func (self *scanner) updatePos() {
	self.pos.col = self.col
	self.pos.line = self.line
}

func (self *scanner) peek() *Token {
	p := *self.pos
	l, c := self.line, self.col
	cp, ch := self.cp, self.ch
	eof := self.isEOF

	ttok, ttv := self.tok, self.tokValue
	ttop := self.tokOp
	ttnb := self.tokNumBase
	ttnt := self.tokNumType

	tok := self.nextToken()

	self.pos = &p
	self.line, self.col = l, c
	self.cp, self.ch = cp, ch
	self.isEOF = eof

	self.tok, self.tokValue = ttok, ttv
	self.tokOp = ttop
	self.tokNumBase = ttnb
	self.tokNumType = ttnt

	return tok
}

func (self *scanner) next() {
	self._next(false)
}

func (self *scanner) nextSingle() {
	self._next(true)
}

func (self *scanner) _checkKeyword() {
	if v, ok := keywordMap[self.tokValue]; ok && self.tok == _IDENTIFIER {
		switch v {
		case _NOT, _AND, _OR:
			self.tok = _OPERATOR
			self.tokOp = v
		default:
			self.tok = v
		}
	}
}

func (self *scanner) _next(single bool) {
redo:
	self.updatePos()

	ch := self.ch
	//println(self.cp, self.col)

	if self.eof() {
		self.tok = _EOF
		return
	}

	if unicode.IsLetter(ch) || ch == '_' {
		self.identifier()
		self._checkKeyword()
		return
	}

	switch ch {
	case '\n':
		self.col = BASE_COL - 1 // nextch()中会增加
		self.line += 1
		self.nextch()
		goto redo

	case ' ', '\t', '\r':
		self.nextch()
		goto redo

	case '(':
		self.nextch()
		self.tok = _LPAREN

	case '[':
		self.nextch()
		self.tok = _LBRACK

	case '{':
		self.nextch()
		self.tok = _LBRACE

	case ')':
		self.nextch()
		self.tok = _RPAREN

	case ']':
		self.nextch()
		self.tok = _RBRACK

	case '}':
		self.nextch()
		self.tok = _RBRACE

	case ',':
		self.nextch()
		self.tok = _COMMA

	case ';':
		self.nextch()
		self.tok = _SEMI

	case ':':
		self.nextch()
		self.tok = _COLON

	case '.':
		self.nextch()

		if single {
			goto sigD
		}

		if self.ch == '.' {
			self.nextch()

			if self.ch == '.' {
				self.nextch()
				self.tok = _DDD
				return
			}

			self.rewind()
		}

	sigD:
		self.tok = _DOT

	case '=':
		self.nextch()

		if single {
			goto sigEq
		}

		if self.ch == '=' {
			self.tok = _OPERATOR
			self.tokOp = _EQ
			self.nextch()
			return
		}

	sigEq:
		self.tok = _OPERATOR
		self.tokOp = _ASSIGN

	case '+':
		self.nextch()
		self.tok = _OPERATOR

		if single {
			goto sigPlus
		}

		if self.ch == '+' {
			self.nextch()
			self.tokOp = _PLUSPLUS
			return
		}

	sigPlus:
		self.tokOp = _PLUS

		goto assignOp

	case '-':
		self.nextch()
		self.tok = _OPERATOR

		if single {
			goto sigSub
		}

		if self.ch == '-' {
			self.nextch()
			self.tokOp = _SUBSUB
			return

		} else if self.ch == '>' {
			self.nextch()
			self.tok = _RARROW
			return
		}

	sigSub:
		self.tokOp = _SUB

		goto assignOp

	case '*':
		self.nextch()
		self.tok, self.tokOp = _OPERATOR, _MUIT

		goto assignOp

	case '%':
		self.nextch()
		self.tok, self.tokOp = _OPERATOR, _MOD

		goto assignOp

	case '/':
		self.nextch()

		if self.ch == '/' {
			self.nextch()
			self.skipLineComment()

			goto redo
		} else if self.ch == '*' {
			self.nextch()
			self.skipBlockComment()

			goto redo
		}

		self.tok, self.tokOp = _OPERATOR, _DIVI
		goto assignOp

	case '&':
		self.nextch()
		self.tok, self.tokOp = _OPERATOR, _BAND

		goto assignOp

	case '~':
		self.nextch()
		self.tok = _OPERATOR
		self.tokOp = _BNG

	case '|':
		self.nextch()
		self.tok, self.tokOp = _OPERATOR, _BOR

		goto assignOp

	case '^':
		self.nextch()
		self.tok, self.tokOp = _OPERATOR, _XOR

		goto assignOp

	case '>':
		self.nextch()

		if single {
			goto sigGTH
		}

		if self.ch == '=' {
			self.nextch()
			self.tok = _OPERATOR
			self.tokOp = _GEQ
			return
		} else if self.ch == '>' {
			self.nextch()
			self.tok = _OPERATOR
			self.tokOp = _RSHR

			goto assignOp
		}

	sigGTH:
		self.tok = _OPERATOR
		self.tokOp = _GTH

	case '<':
		self.nextch()

		if single {
			goto sigLTH
		}

		if self.ch == '=' {
			self.nextch()
			self.tok = _OPERATOR
			self.tokOp = _LEQ
			return

		} else if self.ch == '<' {
			self.nextch()
			self.tok = _OPERATOR
			self.tokOp = _LSHR
			goto assignOp

		} else if self.ch == '-' {
			self.nextch()
			self.tok = _LARROW
			return
		}

	sigLTH:
		self.tok = _OPERATOR
		self.tokOp = _LTH

	case '\'':
		self.nextch()
		self.tokValue = string(self.ch)
		self.nextch()

		if self.ch != '\'' {
			self.syntaxError(
				"except ' ", true)
		}
		self.nextch()
		self.tok = _CHAR

	case '"':
		self.parseString(false)

	case '`':
		self.parseString(true)

	case '!':
		self.nextch()

		if self.ch == '=' {
			self.nextch()
			self.tok = _OPERATOR
			self.tokOp = _UEQ
			return
		}

		self.tok = _OPERATOR
		self.tokOp = _NOT

	case '0', '1', '2', '3', '4',
		'5', '6', '7', '8', '9':
		self.number()

	default:
		self.syntaxError(
			fmt.Sprintf("Invalid character : '%s' (%d), ignored.",
				string(self.ch), self.ch),
			false)
		self.nextch()
		goto redo
	}

	return

assignOp:
	if self.ch == '=' {
		self.nextch()
		self.tok = _OPERATOR
		self.tokOp, self.tokAssignOp = _ASSIGN_OP, self.tokOp
	} else {
		self.tok = _OPERATOR
	}
}

func (self *scanner) number() {
	hasPoint := false

	base := 10 // -1 if decimal
	ntype := _INTEGER
	btype := _ORD

	buf := []rune{}

	if self.ch == '0' {
		self.nextch()

		switch self.ch {
		case 'x', 'X':
			base = 16
			btype = _HEX
			self.nextch()
		case 'o', 'O':
			base = 8
			btype = _OCT
			self.nextch()
		case 'b', 'B':
			base = 2
			btype = _BIN
			self.nextch()
		case '.':
			self.rewind() // back to 0
		default:
			if unicode.IsNumber(self.ch) {
				self.syntaxError("Number starts with 0", true)
			}
			buf = append(buf, '0')
		}
	}

	switch base {
	case 10: // may be DOUBLE
		for {
			if self.ch == '.' {
				if hasPoint {
					self.syntaxError(
						"Invalid decimal", true)
				}
				hasPoint = true
				ntype = _DOUBLE
				base = -1

				buf = append(buf, self.ch)
				self.nextch()

			} else if unicode.IsNumber(self.ch) {
				buf = append(buf, self.ch)
				self.nextch()
			} else {
				goto checkSuffix
			}
		}
	case 16:
		for {
			if isHex(self.ch) {
				buf = append(buf, self.ch)
				self.nextch()
			} else {
				goto checkSuffix
			}
		}
	case 8:
		for {
			if isOct(self.ch) {
				buf = append(buf, self.ch)
				self.nextch()
			} else {
				goto checkSuffix
			}
		}
	}

checkSuffix:
	switch self.ch {
	case 'f', 'F':
		ntype = _FLOAT
		self.nextch()

	case 'l', 'L':
		if base == -1 {
			self.syntaxError(
				"'L' or 'l' before decimal", true)
		}
		self.nextch()
		ntype = _LONG
	}

	self.checkNumber(btype, ntype, string(buf))
	self.tok = _NUMBER
	self.tokNumBase = btype
	self.tokNumType = ntype
	self.tokValue = string(buf)
}

func (self *scanner) skipLineComment() {
	for !self.eof() {
		if self.ch == '\n' {
			self.col = BASE_COL - 1
			self.line++
			self.nextch()
			break
		}
		self.nextch()
	}
}

func (self *scanner) skipBlockComment() {
	for {
		if self.eof() {
			self.syntaxError(
				"Block comment has not been terminated", true)
		}

		if self.ch == '*' {
			self.nextch()
			if self.ch == '/' {
				self.nextch()
				break
			}
			continue
		}
		if self.ch == '\n' {
			self.col = BASE_COL - 1
			self.line++
			self.nextch()
			continue
		}

		self.nextch()
	}
}

func (self *scanner) identifier() {
	buf := make([]rune, 0)
	// var tmp rune

	buf = append(buf, self.ch)

	for !self.eof() {
		self.nextch()

		if unicode.IsLetter(self.ch) || self.ch == '_' || unicode.IsNumber(self.ch) {
			buf = append(buf, self.ch)
			continue
		}

		break
	}

	self.tok = _IDENTIFIER
	self.tokValue = string(buf)
}

func (self *scanner) parseString(raw bool) {
	self.nextch()

	buf := make([]rune, 0)

	endch := '"'

	if raw {
		endch = '`'
	}

	for {
		if self.ch == '\\' {
			ech, ok := self.escape()
			if !ok { // may be endch
				if self.ch == endch {
					self.nextch()
					buf = append(buf, endch)
				} else {
					buf = append(buf, []rune{'\\', self.ch}...)
				}
			} else {
				buf = append(buf, ech)
			}

			continue
		} else if self.ch == endch {
			self.nextch()
			break
		} else if self.ch == '\n' {
			if raw {
				self.col = BASE_COL - 1
				self.line += 1
			} else {
				self.syntaxError(
					"EOL while scanning string literal", true)
			}
		}

		if self.eof() {
			self.syntaxError(
				"EOF while scanning string literal", true)
		}

		buf = append(buf, self.ch)
		self.nextch()
	}

	self.tokValue = string(buf)
	self.tok = _STRING
}

func (self *scanner) escape() (rune, bool) {
	if self.ch != '\\' {
		return 0, false
	}

	self.nextch()

	switch self.ch {
	case 'a':
		self.nextch()
		return '\a', true
	case 'b':
		self.nextch()
		return '\b', true
	case 'n':
		self.nextch()
		return '\n', true
	case 'u':
		p := self.peekString(4)

		if len(p) != 4 {
			self.syntaxError(
				"Unknown unicode character", true)
		}

		n, err := strconv.ParseUint(p, 16, 32)
		self.nextch()

		if err != nil {
			self.syntaxError(
				"Unknown unicode character", true)
		}

		self.movcp(4)

		return rune(n), true
	}
	return 0, false
}

func (self *scanner) checkNumber(base numBase, ntype numType, numv string) {
	size := 0
	switch ntype {
	case _INTEGER:
		size = 32
	case _LONG:
		size = 64
	case _FLOAT:
		size = -32
	case _DOUBLE:
		size = -64
	}

	if size > 0 {
		_, err := strconv.ParseInt(numv, base, size)
		if err != nil {
			goto toLarge
		}
	} else {
		_, err := strconv.ParseFloat(numv, -size)
		if err != nil {
			size = -size
			goto toLarge
		}
	}

	return

toLarge:
	self.syntaxError(
		fmt.Sprintf("Number too large (base : %d bit : %d)", base, size), true)
}

func isHex(ch rune) bool {
	chl := unicode.ToLower(ch)
	return (chl >= 97 && chl <= 102) || (ch >= 48 && ch <= 57)
}

func isOct(ch rune) bool {
	switch ch {
	case '0', '1', '2', '3', '4', '5', '6', '7':
		return true
	}
	return false
}

func TestScanner(r io.Reader) {
	s := new(scanner)
	s.init(r, "TEST")

	tokf := "< Token %s%s >"

	for {

		s.next()

		// t := s.peek()
		// fmt.Println("Peek : " + getTokenName(t.kind))

		switch s.tok {
		case _IDENTIFIER, _CHAR, _STRING:
			fmt.Printf(tokf, getTokenName(s.tok),
				" value = "+strconv.Quote(s.tokValue)+" real = "+s.tokValue)

		case _ASSIGN_OP, _OPERATOR:
			fmt.Printf(tokf, getTokenName(s.tok),
				" op : "+getOperatorName(s.tokOp))

		case _NUMBER:
			fmt.Printf(tokf, getTokenName(s.tok),
				" value = "+s.tokValue+" base = "+fmt.Sprintf("%d", s.tokNumBase)+
					" type = "+string(s.tokNumType))

		default:
			fmt.Printf(tokf, getTokenName(s.tok), "")
		}

		fmt.Println(" " + s.pos.String())

		if s.tok == _EOF {
			break
		}
	}
}
